// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/// Helper library for creating external AST nodes during inference.

import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/names.dart';

import '../kernel/internal_ast.dart';

/// Creates an invocation of the [target] constructor with the given
/// [arguments].
ConstructorInvocation createConstructorInvocation(
    Constructor target, Arguments arguments,
    {required int fileOffset}) {
  return new ConstructorInvocation(target, arguments)..fileOffset = fileOffset;
}

/// Creates a static invocation of [target] with the given arguments.
StaticInvocation createStaticInvocation(Procedure target, Arguments arguments,
    {required int fileOffset}) {
  return new StaticInvocation(target, arguments)..fileOffset = fileOffset;
}

/// Creates a `== null` test on [expression].
EqualsNull createEqualsNull(Expression expression, {required int fileOffset}) {
  return new EqualsNull(expression)..fileOffset = fileOffset;
}

/// Creates a boolean literal of [value].
BoolLiteral createBoolLiteral(bool value, {required int fileOffset}) {
  return new BoolLiteral(value)..fileOffset = fileOffset;
}

/// Creates an integer literal of [value].
Expression createIntLiteral(CoreTypes coreTypes, int value,
    {required int fileOffset}) {
  if (value < 0) {
    /// The web backends need this to be encoded as a unary minus on the
    /// positive value.
    return new InstanceInvocation(
        InstanceAccessKind.Instance,
        new IntLiteral(-value)..fileOffset = fileOffset,
        unaryMinusName,
        new Arguments([])..fileOffset = fileOffset,
        interfaceTarget: coreTypes.intUnaryMinus,
        functionType: coreTypes.intUnaryMinus.getterType as FunctionType)
      ..fileOffset = fileOffset;
  } else {
    return new IntLiteral(value)..fileOffset = fileOffset;
  }
}

/// Creates a string literal of [value].
StringLiteral createStringLiteral(String value, {required int fileOffset}) {
  return new StringLiteral(value)..fileOffset = fileOffset;
}

/// Creates a null literal.
NullLiteral createNullLiteral({required int fileOffset}) {
  return new NullLiteral()..fileOffset = fileOffset;
}

/// Creates a conditional expression of the [condition] and the [then] and
/// [otherwise] branches with the given [staticType] of the resulting
/// expression.
ConditionalExpression createConditionalExpression(
    Expression condition, Expression then, Expression otherwise,
    {required DartType staticType, required int fileOffset}) {
  return new ConditionalExpression(condition, then, otherwise, staticType)
    ..fileOffset = fileOffset;
}

/// Creates a [Let] of [variable] with the given [body] using
/// `variable.fileOffset` as the file offset for the let.
Let createLet(VariableDeclaration variable, Expression body) {
  return new Let(variable, body)..fileOffset = variable.fileOffset;
}

/// Creates a [Let] with the [effect] as the variable initializer and the
/// [result] as the body of the [Let] expression and using
/// `effect.fileOffset` as the file offset for the let.
Let createLetEffect({required Expression effect, required Expression result}) {
  return new Let(createVariableCache(effect, const DynamicType()), result)
    ..fileOffset = effect.fileOffset;
}

/// Creates a [VariableDeclaration] for caching [expression] of the static
/// [type] using `expression.fileOffset` as the file offset for the declaration.
VariableDeclaration createVariableCache(Expression expression, DartType type) {
  return new VariableDeclaration.forValue(expression, type: type)
    ..fileOffset = expression.fileOffset;
}

/// Creates an uninitialized [VariableDeclaration] of the static [type].
VariableDeclaration createUninitializedVariable(DartType type,
    {required int fileOffset}) {
  return new VariableDeclaration(null, type: type, isSynthesized: true)
    ..fileOffset = fileOffset;
}

/// Creates an initialized (but mutable) [VariableDeclaration] of the static
/// [type].
VariableDeclaration createInitializedVariable(
    Expression expression, DartType type,
    {required int fileOffset, String? name}) {
  return new VariableDeclaration(name,
      initializer: expression, type: type, isSynthesized: true)
    ..fileOffset = fileOffset;
}

/// Creates a [VariableDeclaration] for [expression] with the static [type]
/// using `expression.fileOffset` as the file offset for the declaration.
// TODO(johnniwinther): Merge the use of this with [createVariableCache].
VariableDeclaration createVariable(Expression expression, DartType type) {
  assert(expression is! ThisExpression);
  return new VariableDeclaration.forValue(expression, type: type)
    ..fileOffset = expression.fileOffset;
}

/// Creates a [VariableGet] of [variable] using `variable.fileOffset` as the
/// file offset for the expression.
VariableGet createVariableGet(VariableDeclaration variable,
    {DartType? promotedType}) {
  return new VariableGet(variable)
    ..fileOffset = variable.fileOffset
    ..promotedType = promotedType;
}

/// Creates a [VariableSet] of [variable] with the [value].
Expression createVariableSet(VariableDeclaration variable, Expression value,
    {bool allowFinalAssignment = false, required int fileOffset}) {
  if (variable is VariableDeclarationImpl && variable.lateSetter != null) {
    return createLocalFunctionInvocation(variable.lateSetter!,
        arguments: createArguments([value], fileOffset: fileOffset),
        fileOffset: fileOffset);
  } else {
    assert(allowFinalAssignment || variable.isAssignable,
        "Cannot assign to variable $variable");
    return new VariableSet(variable, value)..fileOffset = fileOffset;
  }
}

/// Creates an invocation of the local function [variable] with the provided
/// [arguments].
LocalFunctionInvocation createLocalFunctionInvocation(
    VariableDeclaration variable,
    {Arguments? arguments,
    required int fileOffset}) {
  return new LocalFunctionInvocation(
      variable,
      arguments ?? // Coverage-ignore(suite): Not run.
          createArguments([], fileOffset: fileOffset)
        ..fileOffset = fileOffset,
      functionType: variable.type as FunctionType)
    ..fileOffset = fileOffset;
}

/// Creates a [Not] of [operand].
Not createNot(Expression operand) {
  return new Not(operand)..fileOffset = operand.fileOffset;
}

/// Creates a logical and expression of [left] and [right].
LogicalExpression createAndExpression(Expression left, Expression right,
    {required int fileOffset}) {
  return new LogicalExpression(left, LogicalExpressionOperator.AND, right)
    ..fileOffset = fileOffset;
}

/// Creates a logical or expression of [left] and [right].
LogicalExpression createOrExpression(Expression left, Expression right,
    {required int fileOffset}) {
  return new LogicalExpression(left, LogicalExpressionOperator.OR, right)
    ..fileOffset = fileOffset;
}

/// Creates an is-test on [operand] against [type].
IsExpression createIsExpression(Expression operand, DartType type,
    {required int fileOffset}) {
  return new IsExpression(operand, type)..fileOffset = fileOffset;
}

/// Creates an as-cast on [operand] against [type].
AsExpression createAsExpression(Expression operand, DartType type,
    {required bool forNonNullableByDefault,
    bool isUnchecked = false,
    bool isCovarianceCheck = false,
    required int fileOffset}) {
  return new AsExpression(operand, type)
    ..fileOffset = fileOffset
    ..isUnchecked = isUnchecked
    ..isCovarianceCheck = isCovarianceCheck;
}

/// Creates a [NullCheck] of [expression].
NullCheck createNullCheck(Expression expression, {required int fileOffset}) {
  return new NullCheck(expression)..fileOffset = fileOffset;
}

/// Creates a block expression using [body] as the body and [value] as the
/// resulting value.
BlockExpression createBlockExpression(Block body, Expression value,
    {required int fileOffset}) {
  return new BlockExpression(body, value)..fileOffset = fileOffset;
}

/// Creates a throw of [expression] using the file offset of [expression] for
/// the throw expression.
Throw createThrow(Expression expression, {bool forErrorHandling = false}) {
  return new Throw(expression)
    ..fileOffset = expression.fileOffset
    ..forErrorHandling = forErrorHandling;
}

/// Creates an [ExpressionStatement] of [expression] using the file offset of
/// [expression] for the file offset of the statement.
ExpressionStatement createExpressionStatement(Expression expression) {
  return new ExpressionStatement(expression)
    ..fileOffset = expression.fileOffset;
}

/// Creates an if statement with the [condition], [then] branch and [otherwise]
/// as the optional else branch.
IfStatement createIfStatement(Expression condition, Statement then,
    {Statement? otherwise, required int fileOffset}) {
  return new IfStatement(condition, then, otherwise)..fileOffset = fileOffset;
}

/// Creates a break statement with the given [target].
BreakStatement createBreakStatement(LabeledStatement target,
    {required int fileOffset}) {
  return new BreakStatement(target)..fileOffset = fileOffset;
}

/// Creates a block containing the [statements].
Block createBlock(List<Statement> statements, {required int fileOffset}) {
  return new Block(statements)
    ..fileOffset = fileOffset
    ..fileEndOffset = fileOffset;
}

/// Creates an [Arguments] object for the [positional] and [named] arguments,
/// and [types] as the type arguments.
Arguments createArguments(List<Expression> positional,
    {List<DartType>? types,
    List<NamedExpression>? named,
    required int fileOffset}) {
  return new Arguments(positional, types: types, named: named)
    ..fileOffset = fileOffset;
}

/// Creates a switch case for the case [expressions] and their corresponding
/// file offsets in [expressionOffsets] with the given [body].
SwitchCase createSwitchCase(
    List<Expression> expressions, List<int> expressionOffsets, Statement body,
    {required bool isDefault, required int fileOffset}) {
  return new SwitchCase(expressions, expressionOffsets, body,
      isDefault: isDefault)
    ..fileOffset = fileOffset;
}

/// Create a switch statement on the [expression] with the given [cases]. If
/// the switch is known to be exhaustive and without a default case,
/// [isExplicitlyExhaustive] should be set to `true`.
///
/// The [expressionType] is the static type of the switch expression.
SwitchStatement createSwitchStatement(
    Expression expression, List<SwitchCase> cases,
    {required bool isExplicitlyExhaustive,
    required int fileOffset,
    required DartType expressionType}) {
  return new SwitchStatement(expression, cases,
      isExplicitlyExhaustive: isExplicitlyExhaustive)
    ..expressionType = expressionType
    ..fileOffset = fileOffset;
}

/// Creates a labeled statement that serves as a label for [statement].
LabeledStatement createLabeledStatement(Statement statement,
    {required int fileOffset}) {
  return new LabeledStatement(statement)..fileOffset = fileOffset;
}
